///<reference path='../SharedWorker.d.ts' />
/* system imports */

import { PilotContextBroker, OptionObserver } from './pilot-context-broker';
import { ThreadCrosContextBroker } from './thread-cros-context-broker';
import { InjectionToken, NgZone, Injectable, Inject, Optional, Injector } from '@angular/core';
import { IContextBroker, IContext, CONTEXT_BROKER_LOGGER, IPersist } from 'p-elite-app-contextbroker';
import { IPilotContextBroker } from './pilot-context-broker-interface';
import { Observer } from 'rxjs/Observer';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import { QNSContext, ProxyInvokeContext, ConnectToPilotContext, PilotFuncInvokeContext, LoginToPilotContext, ProxyLoginCommandContext, ProxyLogoutCommandContext, RemoteProxyInvokeContext, PublishBOContext } from '../model/contexts';
import { Subject } from "rxjs/Subject";
import { ReplaySubject } from "rxjs/ReplaySubject";

export interface IWorkerUrl {
    workerUrl?: string;
    qnsWorkerUrl?: string;
}

@Injectable()
export class EliteContextBroker implements IContextBroker, IPilotContextBroker {
    
    private _threadCrosContextBroker: IContextBroker;
    private _pilotContextBroker: IPilotContextBroker;
    private useSharedWorker: boolean;
    private workerUrl: IWorkerUrl;
    private logger: any;
    private demoteStrategy: Function;

    constructor(
        private zone: NgZone,
        private injector: Injector
    ) {
        this.demoteStrategy = injector.get<Function>(SHAREDWORKER_DEMOTE_STRATEGY, null) || this.demoteWorker;
        if (this.demoteStrategy() === true) {
            this.useSharedWorker = false;
        } else {
            this.useSharedWorker = injector.get<boolean>(USE_SHARED_WORKER, null);
        }
        this.workerUrl = injector.get(SHARED_WORKER_URL, null);
        this.logger = injector.get(CONTEXT_BROKER_LOGGER, null);
    }

    public get pilotObserver(): Subject<any> {
        return this.createPilotContextBroker()
            .pilotObserver;
    }

    public get pilotLoginInfoObserver(): ReplaySubject<any> {
        return this.createPilotContextBroker()
            .pilotLoginInfoObserver;
    }

    public registerProvider(context: IContext): void {
        this.createThreadCrosContextBroker()
            .registerProvider(context);
    }

    public registerObservableProvider<T extends IContext>(contextClass: string, contextProvider: Observable<T>): Subscription {
        return this.createThreadCrosContextBroker()
            .registerObservableProvider(contextClass, contextProvider);
    }

    public registerListener<T extends IContext>(contextClass: string, contextObserver: Observer<T>): Subscription {
        return this.createThreadCrosContextBroker()
            .registerListener(contextClass, contextObserver);
    }

    public registerContextListner<T extends IContext>(contextClass: string): Observable<T> {
        return this.createThreadCrosContextBroker()
            .registerContextListner(contextClass);
    }

    public cancelSubscription(subscription: Subscription, requestId?: string): void {
        if (requestId) {
            this.createPilotContextBroker()
                .cancelSubscription(subscription, requestId);
            return;
        }

        return this.createThreadCrosContextBroker()
            .cancelSubscription(subscription);
    }

    public query(context: QNSContext, optionObserver?: OptionObserver): Observable<any> {
        return this.createPilotContextBroker()
            .query(context, optionObserver);
    }

    public sub(context: QNSContext, optionObserver?: OptionObserver): Observable<any> {
        return this.createPilotContextBroker()
            .sub(context, optionObserver);
    }

    public qns(context: QNSContext, optionObserver?: OptionObserver): Observable<any> {
        return this.createPilotContextBroker()
            .qns(context, optionObserver);
    }

    public invokeProxy(context: ProxyInvokeContext, optionObserver?: OptionObserver): Observable<any> {
        return this.createPilotContextBroker()
            .invokeProxy(context, optionObserver);
    }

    public connectToPilot(contextToPilot: ConnectToPilotContext): void {
        return this.createPilotContextBroker()
            .connectToPilot(contextToPilot);
    }

    public loginToPilot(loginToPilotContext: LoginToPilotContext): Promise<any> {
        return this.createPilotContextBroker().loginToPilot(loginToPilotContext);
    }

    public uuid(): string {
        return this.createPilotContextBroker()
            .uuid();
    }

    public setUpOptionObserver(optionObserver: OptionObserver) {
        return this.createPilotContextBroker()
            .setUpOptionObserver(optionObserver);
    }

    public disconnect(): void {
        this.createPilotContextBroker()
            .disconnect();
    }

    public getPersitContext?<T extends IPersist>(contextClass: string): Observable<T> {
        return this.createThreadCrosContextBroker()
            .getPersitContext(contextClass);
    }

    public invokePilotFunc(context: PilotFuncInvokeContext): Promise<any> {
        return this.createPilotContextBroker()
            .invokePilotFunc(context);
    }

    public proxyLogin(context: ProxyLoginCommandContext): Promise<any> {
       return this.createPilotContextBroker()
            .proxyLogin(context);
    }
    public proxyLogout(context: ProxyLogoutCommandContext): Promise<any> {
       return this.createPilotContextBroker()
            .proxyLogout(context);
    }
    public invokeRemoteProxy(context: RemoteProxyInvokeContext): Promise<any> {
        return this.createPilotContextBroker()
            .invokeRemoteProxy(context);
    }
    public publish(context: PublishBOContext): Promise<any> {
       return this.createPilotContextBroker()
            .publish(context);
    }

    private createThreadCrosContextBroker(): IContextBroker {
        if (!this._threadCrosContextBroker) {
            this._threadCrosContextBroker = new ThreadCrosContextBroker(
                this.zone,
                this.workerUrl && this.workerUrl.workerUrl,
                this.useSharedWorker,
                this.logger);
        }
        return this._threadCrosContextBroker
    }

    private createPilotContextBroker(): IPilotContextBroker {

        if (!this._pilotContextBroker) {
            this._pilotContextBroker = new PilotContextBroker(
                this.zone,
                this.workerUrl && this.workerUrl.qnsWorkerUrl,
                this.useSharedWorker,
                this.logger);
        }
        return this._pilotContextBroker
    }

    private demoteWorker(): boolean {
        try {
            return !((typeof SharedWorker) === (typeof Function));
        } catch (error) {
            return true;
        }
    }
}


export const IS_PILOT_SHARED_WORKER = new InjectionToken('IS_PILOT_SHARED_WORKER');
export const SHARED_WORKER_URL = new InjectionToken('SHARED_WORKER_URL');
export const USE_SHARED_WORKER = new InjectionToken('USE_SHARED_WORKER');
export const SHAREDWORKER_DEMOTE_STRATEGY = new InjectionToken('SHAREDWORKER_DEMOTE_STRATEGY');
